using System;
using System.Collections.Generic;
using System.Text;
using Heaven.Mathematics;

namespace Heaven.Geometry.Procedural
{
    /// <summary>
    /// This is a class which performs 
    /// tesselation with the given parameters
    /// </summary>
    public class Tessellator
    {
        #region Properties

        /// <summary>
        /// The main parametrized function
        /// </summary>
        public TesselatorFunction Function = null;

        /// <summary>
        /// The derivative of the function by U.
        /// It is required for noramals calculation
        /// </summary>
        public TesselatorFunction DerivativeU = null;


        /// <summary>
        /// The derivative of the function by V.
        /// It is required for noramals calculation
        /// </summary>
        public TesselatorFunction DerivativeV = null;

        /// <summary>
        /// Resolution of tesselation by U
        /// </summary>
        public double dU = 0.3;

        /// <summary>
        /// Resolution of tesselation by V
        /// </summary>
        public double dV = 0.3;

        /// <summary>
        /// The start value of U
        /// </summary>
        public double StartU = 0;

        /// <summary>
        /// The start value of V
        /// </summary>
        public double StartV = 0;
        
        /// <summary>
        /// The end value of U
        /// </summary>
        public double EndU = 1;

        /// <summary>
        /// The end value of V
        /// </summary>
        public double EndV = 1;

        /// <summary>
        /// Are noramls required to generate?
        /// </summary>
        public bool NeedNormals = true;

        /*/// <summary>
        /// Are binormals required to generate?
        /// </summary>
        public bool NeedBinormals = true;

        /// <summary>
        /// Are tangents required to generate?
        /// </summary>
        public bool NeedTangents = true;*/

        #endregion

        #region Methods

        /// <summary>
        /// Performs tesselation
        /// </summary>
        /// <returns>Polygonal object</returns>
        public Polygonal Tesselate()
        {
            // The function is required, the derivative can be missed
            if (Function == null)
            {
                throw new ArgumentException("Tesselation requires the function, please set the proper field");
            }
            // Start values must be lower then end values
            if ((StartU > EndU)||(StartV > EndV))
            {
                throw new ArgumentException("Tesselation required that start values must be lower then end values");
            }

            Polygonal polygonal = new Polygonal();

            // Recalculate dU and dV in order to get reduction ratio
            int numberU = (int)((EndU - StartU) / dU);
            int numberV = (int)((EndV - StartV) / dV);
            double dUrecalulated = dU + ((EndU - StartU) - dU * numberU) / numberU;
            double dVrecalulated = dV + ((EndV - StartV) - dV * numberV) / numberV;

            // Validating
            if ((numberU == 0) || (numberV == 0)) throw new ArgumentException("Tessellator error. Check the end, start and dU/dV parameters, (EndX - StartX) / dX = 0");

            // Generating vertices
            double u = StartU - dUrecalulated;
            do
            {
                u += dUrecalulated;
                double v = StartV - dVrecalulated;
                do
                {
                    v += dVrecalulated;
                    polygonal.AddVertex(GetVertex(u, v));
                } 
                while (v + 0.0005 < EndV);
            }
            while (u + 0.0005 < EndU);

            // Generating faces
            for(int i = 0; i < numberU; i++)
            {
                for (int j = 0; j < numberV; j++)
                {
                    // Clockwise enumeration (!)
                    polygonal.AddTriangle(
                        (i + 0) * (numberV + 1) + (j + 1),
                        (i + 0) * (numberV + 1) + (j + 0),
                        (i + 1) * (numberV + 1) + (j + 1));
                    polygonal.AddTriangle(
                        (i + 1) * (numberV + 1) + (j + 0),
                        (i + 1) * (numberV + 1) + (j + 1),
                        (i + 0) * (numberV + 1) + (j + 0));
                }
            }
            
            return polygonal;
        }

        /// <summary>
        /// Tesselates only a cap using U parameter
        /// </summary>
        /// <param name="fixedV">A fixed value of V</param>
        /// <param name="faceInversion">Inversion of faces</param>
        /// <returns>Polygonal object</returns>
        public Polygonal GetCapU(double fixedV, bool faceInversion)
        {
            return GetCap(1, fixedV, faceInversion);
        }

        /// <summary>
        /// Tesselates only a cap using V parameter
        /// </summary>
        /// <param name="fixedU">A fixed value of U</param>
        /// <param name="faceInversion">Inversion of faces</param>
        /// <returns>Polygonal object</returns>
        public Polygonal GetCapV(double fixedU, bool faceInversion)
        {
            return GetCap(2, fixedU, faceInversion);
        }

        /// <summary>
        /// Tesselates only a cap using U parameter
        /// </summary>
        /// <param name="parameter">1 - u, 2 - v</param>
        /// <param name="fixedValue">A fixed value on other parameter</param>
        /// <param name="faceInversion">Inversion of faces</param>
        /// <returns>Polygonal object</returns>
        Polygonal GetCap(int parameter, double fixedValue, bool faceInversion)
        {
            // The function is required, the derivative can be missed
            if (Function == null)
            {
                throw new ArgumentException("Tesselation requires the function, please set the proper field");
            }
            // Start values must be lower then end values
            if ((StartU > EndU) || (StartV > EndV))
            {
                throw new ArgumentException("Tesselation required that start values must be lower then end values");
            }

            Polygonal polygonal = new Polygonal();

            // Recalculate dU and dV in order to get reduction ratio
            int numberU = (int)((EndU - StartU) / dU);
            int numberV = (int)((EndV - StartV) / dV);
            double dUrecalulated = dU + ((EndU - StartU) - dU * numberU) / numberU;
            double dVrecalulated = dV + ((EndV - StartV) - dV * numberV) / numberV;

            // Generating vertices
            List<UnifiedVertex> vertices = new List<UnifiedVertex>();
            double t = (parameter == 1) ? StartU - dUrecalulated : StartV - dVrecalulated;
            do
            {
                t += (parameter == 1) ? dUrecalulated : dVrecalulated;
                if (parameter == 1) vertices.Add(GetVertex(t, fixedValue));
                else vertices.Add(GetVertex(fixedValue, t));
            }
            while (t + 0.0005 < ((parameter == 1) ? EndU : EndV));
            
            // Correcting normals
            if (faceInversion)
            {
                Vector3 normal = (vertices[0].Position - vertices[1].Position).CrossProduct(
                    vertices[1].Position - vertices[2].Position);
                for (int i = 0; i < vertices.Count; i++)
                {
                    vertices[i].Normal = Vector3.Normalize(normal);
                }
                polygonal.AddVertices(vertices.ToArray());

                for (int i = 1; i < vertices.Count - 1; i++)
                {
                    polygonal.AddTriangle(0, i + 1, i);
                }
            }
            else
            {
                Vector3 normal = (vertices[0].Position - vertices[1].Position).CrossProduct(
                    vertices[2].Position - vertices[1].Position);
                for (int i = 0; i < vertices.Count; i++)
                {
                    vertices[i].Normal = Vector3.Normalize(normal);
                }
                polygonal.AddVertices(vertices.ToArray());

                for (int i = 1; i < vertices.Count - 1; i++)
                {
                    polygonal.AddTriangle(0, i, i + 1);
                }
            }
            return polygonal;
        }

        UnifiedVertex GetVertex(double u, double v)
        {
            UnifiedVertex vertex = new UnifiedVertex();
            vertex.Position = Function(u, v);

            Vector3 derivativeU = DerivativeU(u, v);
            Vector3 derivativeV = DerivativeV(u, v);

            if (NeedNormals)
            {
                Vector3 crossProduct = Vector3.CrossProduct(derivativeU, derivativeV);
                vertex.Normal = crossProduct == Vector3.Zero ? Vector3.YAxis : Vector3.Normalize(crossProduct);
            }
            return vertex;
        }

        double GetFractionalPart(double value)
        {
            return value - Math.Ceiling(value);
        }

        #endregion
    }

    /// <summary>
    /// This is a delegate for parametrized function f(u,v)
    /// </summary>
    /// <param name="u">U</param>
    /// <param name="v">V</param>
    /// <returns>Result of the function</returns>
    public delegate Vector3 TesselatorFunction(double u, double v);
}
